package net.proef.sample.service;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import net.proef.sample.domain.Address;
import net.proef.sample.domain.Author;
import net.proef.sample.domain.Book;
import net.proef.sample.domain.Publisher;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.googlecode.ehcache.annotations.Cacheable;
import com.thoughtworks.xstream.XStream;

@Service
public class LocalXmlStoreBookService implements BookService, InitializingBean {

	private Map<String, Book> bookMap = Maps.newHashMap();
	private Multimap<String, Book> authorIdToBook = ArrayListMultimap.create();
	private Map<String, Author> authorMap = Maps.newHashMap();

	private Resource source;
	private static final String fakeData = "<books><book><isbn>0596009208</isbn><title>Head First Java</title><edition>2</edition><pages>688</pages><published>May 11, 2009</published><authors><author><authorId>1</authorId><name>Kathy Sierra</name><email>ksierra@somedomain.com</email></author><author><authorId>2</authorId><name>Bert Bates</name><email>bbates@somedomain.com</email></author></authors><publisher><name>O'Reilly Media</name><address><address>1005 Gravenstein Highway North</address><city>Sebastopol</city><state>CA</state><zip>95472</zip></address></publisher></book><book><isbn>0321349601</isbn><title>Java Concurrency in Practice</title><edition>1</edition><pages>384</pages><published>May 19, 2006</published><authors><author><authorId>3</authorId><name>Brian Goetz</name><email>bgoetz@anotherdomain.com</email></author><author><authorId>4</authorId><name>Joshua Bloch</name><email>jbloch@anotherdomain.com</email></author></authors><publisher><name>Addison-Wesley Professional</name><address><address>1234 Main St</address><city>Boston</city><state>MA</state><zip>02103</zip></address></publisher></book><book><isbn>0321356683</isbn><title>Effective Java</title><edition>2</edition><pages>384</pages><published>May 28, 2008</published><authors><author><authorId>4</authorId><name>Joshua Bloch</name><email>jbloch@anotherdomain.com</email></author></authors><publisher><name>Prentice Hall PTR</name><address><address>P.O. Box 2500</address><city>Lebanon</city><state>IN</state><zip>46052</zip></address></publisher></book></books>";

	@SuppressWarnings("unchecked")
	public void afterPropertiesSet() {
		XStream xs = new XStream();
		xs.processAnnotations(Book.class);
		xs.processAnnotations(Author.class);
		xs.processAnnotations(Publisher.class);
		xs.processAnnotations(Address.class);
		xs.alias("books", List.class);

		List<Book> books = Collections.emptyList();

		if (source != null) {
			try {
				books = (List<Book>) xs.fromXML(source.getInputStream());
			} catch (IOException e) {
				throw new IllegalArgumentException(e);
			}
		} else {
			books = (List<Book>) xs.fromXML(fakeData);
		}

		for (Book book : books) {
			bookMap.put(book.getIsbn(), book);
			for (Author a : book.getAuthors()) {
				authorIdToBook.put(a.getAuthorId(), book);
				authorMap.put(a.getAuthorId(), a);
			}
		}
	}

	@Override
	@Cacheable(cacheName = "bookCache")
	public Book getBookByIsbn(String isbn) {
		return bookMap.get(isbn);
	}

	@Override
	@Cacheable(cacheName = "bookCache")
	public List<Book> getBooksByAuthor(String authorId) {
		return Lists.newArrayList(authorIdToBook.get(authorId));
	}

	@Override
	public List<Author> getAllAuthors() {
		return Lists.newArrayList(authorMap.values());
	}

	@Override
	public List<Book> getAllBooks() {
		return Lists.newArrayList(bookMap.values());
	}

	@Override
	@Cacheable(cacheName = "authorCache")
	public Author getAuthorById(String authorId) {
		return authorMap.get(authorId);
	}

	@Autowired(required = true)
	public void setSource(Resource source) {
		this.source = source;
	}
}